use super::{err, err_value, json, throw_err, Collect, ECode, KeyNode, VBase, VMap, Value, VecV};
use chrono::prelude::*;

pub async fn gen_psn<'a>(
    rule_sn: &str,
    collect: &Collect<'a>,
    md: &mut VecV,
) -> Result<String, Value> {
    let rule = collect.item_sn("code", rule_sn).await?;
    let node = rule.ck_array("v")?;

    let now: DateTime<Local> = Local::now();

    let mut code = Vec::new();

    for p in node.iter() {
        let part = p.ckobj()?;

        let name = part.ck_str("name")?;
        let tp = part.ck_str("type")?;

        match tp {
            "fix" => {
                let num = part.ck_i64("num")? as usize;
                let val = part.ck_str("val")?;

                if val.len() != num {
                    throw_err!(ECode::Custom, length_err(name, val, num));
                }

                code.push(val.to_string());
            }
            "date" => {
                let data = part.ck_str("date")?;
                let tmp = if data == "gb34014" {
                    gb34014_time()?
                } else if data == "gb16735" {
                    gb_time_gb16735()?
                } else if data == "YYM" {
                    gb_time_yym()?
                } else if data == "tj" {
                    tj_time()
                } else {
                    now.format(data).to_string()
                };

                code.push(tmp);
            }
            "gnum1" | "gnum2" | "gnum3" | "gnum4" | "num" => {
                let num = part.ck_i64("num")? as usize;
                let fnum = part.ck_i64("val")?;
                let mut z = fnum;

                let dcur = match part.ck_str("range")? {
                    "day" => now.format("%y%m%d").to_string(),
                    _ => "no".to_string(),
                };
                let dcur = dcur.as_str();

                if tp == "num" {
                    let tmp = match rule.k_str("cur") {
                        Some(v) => v,
                        None => "no",
                    };

                    // in same range
                    if dcur == "no" || dcur == tmp {
                        if let Some(n) = rule.k_i64("cnum") {
                            z = n + 1;

                            let max = part.ck_i64("max")?;
                            if z > max {
                                z = fnum;
                            }
                        }
                    }

                    md.push(json!({
                        "tp": "code",
                        "sn": rule_sn,
                        "val": {
                            "cur": dcur,
                            "cnum": z,
                        }
                    }));

                    let tmp = format!("{:0>len$}", z, len = num);

                    code.push(tmp);
                } else {
                    let num = part.ck_i64("num")? as usize;
                    let k_cur_gnum = collect.key_sn("cur", tp)?;

                    let (tmp, cnum) = match collect.get_file("cur", &k_cur_gnum) {
                        Ok(v) => {
                            let v = v.ckobj()?;

                            let v_cur = v.ck_str("cur")?;
                            let v_cnum = v.ck_i64("cnum")?;

                            (v_cur.to_string(), v_cnum)
                        }
                        Err(_) => ("no".to_string(), 0),
                    };

                    // in same range
                    if dcur == "no" || dcur == tmp {
                        z = cnum + 1;

                        let max = part.ck_i64("max")?;
                        if z > max {
                            z = fnum;
                        }
                    }

                    md.push(json!({
                        "update": true,
                        "sn": k_cur_gnum,
                        "val": {
                            "cur": dcur,
                            "cnum": z,
                        }
                    }));

                    let tmp = format!("{:0>len$}", z, len = num);

                    code.push(tmp);
                }
            }
            _ => (),
        }
    }

    let psn = code.join("");

    Ok(psn.to_string())
}

fn length_err(name: &str, data: &str, num: usize) -> String {
    format!("length error, {}: {}, need: {} ", name, data.len(), num)
}

fn gb_time_yym() -> Result<String, Value> {
    let tab_month = vec![
        "1", // 1
        "2", // 2
        "3", // 3
        "4", // 4
        "5", // 5
        "6", // 6
        "7", // 7
        "8", // 8
        "9", // 9
        "A", // 10
        "B", // 11
        "C", // 12
    ];

    let local: DateTime<Local> = Local::now();
    let year = local.year().to_string();
    let (_, b) = year.split_at(year.len() - 2);

    let month = local.month() - 1;
    let month = tab_month[month as usize];

    Ok(format!("{}{}", b, month))
}

fn gb_time_gb16735() -> Result<String, Value> {
    let tab_year = vec![
        "9", // 2009
        "A", // 2010
        "B", // 2011
        "C", // 2012
        "D", // 2013
        "E", // 2014
        "F", // 2015
        "G", // 2016
        "H", // 2017
        "J", // 2018
        "K", // 2019
        "L", // 2020
        "M", // 2021
        "N", // 2022
        "P", // 2023
        "R", // 2024
        "S", // 2025
        "T", // 2026
        "V", // 2027
        "W", // 2028
        "X", // 2029
        "Y", // 2030
        "1", // 2031
        "2", // 2032
        "3", // 2033
        "4", // 2034
        "5", // 2035
        "6", // 2036
        "7", // 2037
        "8", // 2038
    ];

    let tab_month = vec![
        "1", // 1
        "2", // 2
        "3", // 3
        "4", // 4
        "5", // 5
        "6", // 6
        "7", // 7
        "8", // 8
        "9", // 9
        "A", // 10
        "B", // 11
        "C", // 12
    ];

    let tab_day = vec![
        "1", // 1
        "2", // 2
        "3", // 3
        "4", // 4
        "5", // 5
        "6", // 6
        "7", // 7
        "8", // 8
        "9", // 9
        "A", // 10
        "B", // 11
        "C", // 12
        "D", // 13
        "E", // 14
        "F", // 15
        "G", // 16
        "H", // 17
        "J", // 18
        "K", // 19
        "L", // 20
        "M", // 21
        "N", // 22
        "P", // 23
        "R", // 24
        "S", // 25
        "T", // 26
        "U", // 27
        "V", // 28
        "W", // 29
        "X", // 30
        "Y", // 31
    ];

    let local: DateTime<Local> = Local::now();
    let mut year = local.year();
    if year > 2038 {
        year = year % 2038 + 2008;
    }

    year -= 2009;
    if year < 0 {
        throw_err!(ECode::Custom, "current year must >= 2009".to_string());
    }

    let year = tab_year[year as usize];

    let month = local.month() - 1;
    let month = tab_month[month as usize];

    let day = local.day() - 1;
    let day = tab_day[day as usize];

    Ok(format!("{}{}{}", year, month, day))
}

pub fn gb34014_time() -> Result<String, Value> {
    let tab_year = vec![
        "9", // 2019
        "A", // 2020
        "B", // 2021
        "C", // 2022
        "D", // 2023
        "E", // 2024
        "F", // 2025
        "G", // 2026
        "H", // 2027
        "J", // 2028
        "K", // 2029
        "L", // 2030
        "M", // 2031
        "N", // 2032
        "P", // 2033
        "R", // 2034
        "S", // 2035
        "T", // 2036
        "V", // 2037
        "W", // 2038
        "X", // 2039
        "Y", // 2040
        "1", // 2041
        "2", // 2042
        "3", // 2043
        "4", // 2044
        "5", // 2045
        "6", // 2046
        "7", // 2047
        "8", // 2048
    ];

    let tab_month = vec![
        "1", // 1
        "2", // 2
        "3", // 3
        "4", // 4
        "5", // 5
        "6", // 6
        "7", // 7
        "8", // 8
        "9", // 9
        "A", // 10
        "B", // 11
        "C", // 12
    ];

    let tab_day = vec![
        "1", // 1
        "2", // 2
        "3", // 3
        "4", // 4
        "5", // 5
        "6", // 6
        "7", // 7
        "8", // 8
        "9", // 9
        "A", // 10
        "B", // 11
        "C", // 12
        "D", // 13
        "E", // 14
        "F", // 15
        "G", // 16
        "H", // 17
        "J", // 18
        "K", // 19
        "L", // 20
        "M", // 21
        "N", // 22
        "P", // 23
        "R", // 24
        "S", // 25
        "T", // 26
        "V", // 27
        "W", // 28
        "X", // 29
        "Y", // 30
        "0", // 31
    ];

    let local: DateTime<Local> = Local::now();
    let mut year = local.year();
    if year > 2048 {
        year = year % 2048 + 2018;
    }

    year -= 2019;
    if year < 0 {
        throw_err!(ECode::Custom, "current year must >= 2019".to_string());
    }

    let year = tab_year[year as usize];

    let month = local.month() - 1;
    let month = tab_month[month as usize];

    let day = local.day() - 1;
    let day = tab_day[day as usize];

    Ok(format!("{}{}{}", year, month, day))
}

pub fn tj_time() -> String {
    let tab_month = vec![
        "A", // 1
        "B", // 2
        "C", // 3
        "D", // 4
        "F", // 5
        "G", // 6
        "H", // 7
        "K", // 8
        "L", // 9
        "M", // 10
        "P", // 11
        "S", // 12
    ];

    let local: DateTime<Local> = Local::now();
    let year = local.year() % 10;

    let month = local.month() - 1;
    let month = tab_month[month as usize];

    format!("{}{}", year, month)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn psn_test_m32() {
        assert_eq!(num_32(0), "0");
        assert_eq!(num_32(31), "Y");
        assert_eq!(num_32(32), "10");
        assert_eq!(num_32(1000), "Y8");
        assert_eq!(num_32(3000), "2WR");
    }
}
